#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

// New includes
#include "util.h"
#include "network.h"

/*
 * THIS IS THE SENDER
 * NOTE: the receiver acts as a client and the multiple senders act
 * as various servers that the receiver needs to connect to.
 *
 * The SENDER is initialized using the following command:
 *
 * sender -p <port> -g <requester port> 
 *                  -r <rate> -q <seq_no> -l <length>
 */

// CONFIGURATION VARIABLES
#define NUM_SENDER_ARGS 19

int in_port;          // Port on which the sender waits for requests
int out_port;         // Port on which the requester is waiting
int rate;             // Number of packets to be sent per second
uint32_t seq_no;      // Initial sequence of the packet exhange
uint32_t length;      // Length of the payload in the packets
char *f_hostname;
int f_port;
int priority;
int timeoutTime;
int window;
fd_set readfds;

int sockfd;
int parse_code;
struct sockaddr_in servaddr, cliaddr;

long last_sent;
long delay_period;

void dg_send(int sockfd, struct sockaddr_in *pcliaddr, socklen_t clilen)
{
    int n = 0;
    socklen_t len;
    int size = 0; // Used to keep track of total data sent
    int i;

    seq_no = 1; // Specification for this part of the project

    /*
     * PULL THE RECVFROM FROM THE LOOP BECAUSE IT WAITS TILL MESSAGE
     * IS OBTAINED;  LOOP ON CONTENTS OF FILE (i.e. until file empty)
     */

    // Setup timespec struct for nanosleep method
    // Used to delay between the sending of packets
    struct timespec rate_spec;
    calculate_rate(rate, &rate_spec);
    //delay_period = calculate_rate_milli(rate);

    while((n = recvfrom(sockfd, em_packet_buf, MAX_PACKET_SIZE,
				0, (struct sockaddr*) pcliaddr, &len)) > 0 ){

        // Validate the recvfrom
        if (n == -1)
        {
            perror(ERROR_RECVFROM);
            exit(1);
        }

	Em_Packet* em_packet;

        // Read and print recieved packet from buffer
	em_packet = (struct Em_Packet*)em_packet_buf;
       	packet = (Packet*)em_packet->payload;

        print_time();
        size += n;
        em_packet_buf[n+1] = '\0'; // Null terminate
        print_em_packet(em_packet);
        print_recv_packet(packet, pcliaddr, n, size);

        // Make sure what was obtained was a request
        if (packet->type != 'R')
        {
            continue;
        }

	// Obtain the window length from the packet
        window = packet->length;

        // Open requested file for reading
	FILE *reqFile;
	reqFile = fopen((char*)packet->payload, "r");

	int acksReceived;          // boolean flag
	int expiredTimeouts = 0;   // boolean flag
	struct timeval timeout;
	timeout.tv_sec = 0;
	timeout.tv_usec = 0; //timeoutTime;
	Window_Element sentPackets[window];
	Em_Packet *tmp_packet; // = (Em_Packet*) sentPackets[i].em_packet;

	int read_len = 0;
        // Loops through tracker.txt to read requested filename and sender info

	/////////////////////////////////////////////////////////

        while ((read_len = fread((char*)packet->payload, 1, length, reqFile)) != 0)
        {
            // Reset acksReceived for the new window
            acksReceived = 0;        

            packet->type = 'D';
            packet->sequence = seq_no++;
            em_packet->length = read_len + sizeof(Packet);
	    memcpy(sentPackets[0].em_packet, (char*)em_packet, em_packet->length + sizeof(Em_Packet));
            sentPackets[0].em_packet[em_packet->length + sizeof(Em_Packet)] = '\0'; // Null terminate

	    // Fill the rest of this window set
	    for(i = 1; i < window; i++){

                //printf("SHOULD BE SKIPPING THIS FOR TEST6");

	    	if((read_len = fread((char*)packet->payload, 1, length, reqFile)) != 0)
	    	{
                    packet->sequence = seq_no++;
                    em_packet->length = read_len + sizeof(Packet);
                    memcpy(sentPackets[i].em_packet, (char*)em_packet, em_packet->length + sizeof(Em_Packet));
                    sentPackets[i].em_packet[em_packet->length + sizeof(Em_Packet)] = '\0'; // Null terminate
	    	}
	    	else{
                    window = i; // Set the window count
	    	    break;
	    	}
	    }

	    // Reset the sentPackets Array
	    for(i = 0; i < window; i++){
                sentPackets[i].to = 0;  // Set this?
                sentPackets[i].times_sent = 0;
                sentPackets[i].acked = 0;
	    }

	    //printf("ACKS_RECV: %d\n", acksReceived);

	    // SEND THE FIRST SET OF PACKETS

                // Setup outgoing send
                struct hostent *outgoing;
                outgoing = gethostbyname(f_hostname);
	        bzero(pcliaddr, sizeof(*pcliaddr));
	        pcliaddr->sin_family = AF_INET;
	        pcliaddr->sin_port = htons(f_port);
	        bcopy((char *)outgoing->h_addr,
	              (char *)&(pcliaddr->sin_addr.s_addr),
		      outgoing->h_length);

		// Loop through window of packets for the initial send
                for(i = 0; i < window; i++){

                    // Prepare to send the packet
                    tmp_packet = (Em_Packet*) sentPackets[i].em_packet;
                    packet = (Packet*)tmp_packet->payload;

                    load_em_packet_IP(em_packet, priority, (char*)&tmp_packet->dest_IP,
                                      tmp_packet->dest_port, (char*)&tmp_packet->src_IP, 
                                      tmp_packet->src_port, sizeof(Packet) + strlen(packet->payload),
                                      packet);

                    len = clilen;

                    // Sleep for requested rate (do before the send to not affect the timeout)
                    // Spin to delay
		    //while(delay_period > (get_milli_time() - last_sent)){}
		    nanosleep(&rate_spec, NULL);

		    // Send the packet out
                    print_time();
	            if ((n = sendto(sockfd, em_packet_buf,
                                    sizeof(Em_Packet) + em_packet->length,
                        	    0, (struct sockaddr*) pcliaddr, len)) == -1)
                    {
	                perror(ERROR_SENDTO);
	                exit(1);
	            }

		    // Set new information for this window element
		    sentPackets[i].to = get_milli_time();
		    last_sent = sentPackets[i].to;
		    sentPackets[i].times_sent++;

		    print_em_packet((Em_Packet*)em_packet_buf);
	            print_sent_packet((Packet*)((Em_Packet*)em_packet->payload), pcliaddr, n);

		} // End loop through window send


	    // While acks are not received
	    while(acksReceived == 0){
                expiredTimeouts = 0; // Being used as a boolean

                // Set up non-blocking check for incoming packets
		int select_status;

		do{
                FD_ZERO(&readfds);
                FD_SET(sockfd, &readfds);
                select_status = select(sockfd+1, &readfds, NULL, NULL, &timeout);

		// If nothing is available to be received...
                if (select_status <= 0)
                {
                    break;
                }

		n = recvfrom(sockfd, em_packet_buf, MAX_PACKET_SIZE,
				0, (struct sockaddr*) pcliaddr, &len);

		if (n == -1)
		{
            	    perror(ERROR_RECVFROM);
	    	    exit(1);
		}

		// TODO: Verify that the receive was an ACK

		print_time();
		size += n;
                em_packet_buf[n] = '\0'; // Null terminate

		// Read received packet from buffer
		em_packet = (struct Em_Packet*)em_packet_buf;
		packet = (Packet*)em_packet->payload;

                print_em_packet(em_packet);
                print_recv_packet(packet, pcliaddr, n, size);

		printf("seq: %d\twindow: %d\tmodulo: %d\n", packet->sequence, window, (packet->sequence-1)%window);

		// Set that an ACK was received for this packet
		sentPackets[(packet->sequence-1)%window].acked = 1;
		acksReceived = 1;

		// Check for unACK packets
		for(i=0;i<window;i++){
		    if (sentPackets[i].acked == 0)
		    {
			acksReceived = 0;
		    }
		}

                // Set the port to the Receiver's port
                (pcliaddr)->sin_port =  htons(f_port);

		} while(select_status > 0);

		// HANDLE TIMEOUTS HERE
                // Setup outgoing send
                struct hostent *outgoing;
                outgoing = gethostbyname(f_hostname);
	        bzero(pcliaddr, sizeof(*pcliaddr));
	        pcliaddr->sin_family = AF_INET;
	        pcliaddr->sin_port = htons(f_port);
	        bcopy((char *)outgoing->h_addr,
	              (char *)&(pcliaddr->sin_addr.s_addr),
		      outgoing->h_length);

		// Loop through window of packets and check the timeouts
                for(i = 0; i < window; i++){

                    long curr_time = get_milli_time();

                    // Check if the timeout has expired for this packet
                    if(curr_time - sentPackets[i].to <= timeoutTime || sentPackets[i].acked == 1)
                    {
                        continue;
                    }
		    // Start counting at zero
                    else if(sentPackets[i].times_sent > 4){

// TEST CODE
tmp_packet = (Em_Packet*) sentPackets[i].em_packet;
packet = (Packet*)tmp_packet->payload;
 printf("NUM SENT PCKTS (#%d): %d\n", packet->sequence, sentPackets[i].times_sent);

                        perror(ERROR_SEND_NUM);
                        exit(1);
                    }
		    
                    printf("At #%d: to = %ld, curr_time = %ld, (diff = %ld) timeout = %d\n", i, sentPackets[i].to, curr_time, curr_time - sentPackets[i].to, timeoutTime);

                    // Prepare to send the packet
                    tmp_packet = (Em_Packet*) sentPackets[i].em_packet;
                    packet = (Packet*)tmp_packet->payload;

                    load_em_packet_IP(em_packet, priority, (char*)&tmp_packet->dest_IP,
                                      tmp_packet->dest_port, (char*)&tmp_packet->src_IP, 
                                      tmp_packet->src_port, sizeof(Packet) + strlen(packet->payload),
                                      packet);

                    len = clilen;

                    // Sleep for requested rate (do before the send to not affect the timeout)
                    // Spin to delay
		    //while(delay_period > (get_milli_time() - last_sent)){}
		    //	                nanosleep(&rate_spec, NULL);

		    nanosleep(&rate_spec, NULL);

		    // Send the packet out
                    print_time();
	            if ((n = sendto(sockfd, em_packet_buf,
                                    sizeof(Em_Packet) + em_packet->length,
                        	    0, (struct sockaddr*) pcliaddr, len)) == -1)
                    {
	                perror(ERROR_SENDTO);
	                exit(1);
	            }

		    // Set new information for this window element
		    sentPackets[i].to = get_milli_time();
		    last_sent = sentPackets[i].to;

		    printf("before: NUM TIMES: %d\n", sentPackets[i].times_sent);
		    sentPackets[i].times_sent++;
		    printf("after: NUM TIMES: %d\n", sentPackets[i].times_sent);



		    print_em_packet((Em_Packet*)em_packet_buf);
	            print_sent_packet((Packet*)((Em_Packet*)em_packet->payload), pcliaddr, n);

		} // End loop through window send

	    }
	} // End of outer read loop

	print_time();
	fclose(reqFile);

        //setup end packet
	load_packet(packet, 'E', 0, 0, "");    

	load_em_packet_IP(em_packet, 
			priority, 
			(char*)&em_packet->dest_IP, 
			em_packet->dest_port, 
			(char*)&em_packet->src_IP, 
			em_packet->src_port, 
			sizeof(Packet) + strlen(packet->payload), 
			packet); 

        (pcliaddr)->sin_family =  AF_INET;
        (pcliaddr)->sin_port =  htons(f_port);

	// Send the end packet
	if((n = sendto(sockfd, em_packet_buf,
			sizeof(Em_Packet) + em_packet->length,
			0, (struct sockaddr*) pcliaddr, len)) == -1)
	{
	    perror(ERROR_SENDTO);
	    exit(1);
	}

        print_em_packet((Em_Packet*)em_packet_buf);
        print_sent_packet((Packet*)((Em_Packet*)em_packet->payload), pcliaddr, n);

	break;
	// Need to receive ACK for END packet?
    }
}

int main(int argc, char *argv[])
{
    // Verify number of command line arguments
    if (argc != NUM_SENDER_ARGS)
    {
        perror(ERROR_SENDER_USAGE);
        exit(1);
    }

    // Parse command line information and set up configurations
    while ((parse_code = getopt(argc, argv, "pgrqlfhit:")) != -1)
    {
       switch (parse_code)
       {
           case 'p':
	       in_port = strtol(argv[optind], NULL, 10);
	       if (in_port <= 1024 || in_port >= 65536)
	       {
                   perror(ERROR_PORT_RANGE);
                   exit(1);
	       }
               break;
           case 'g':
               out_port = strtol(argv[optind], NULL, 10);
	       if (out_port <= 1024 || out_port >= 65536)
	       {
                   perror(ERROR_PORT_RANGE);
                   exit(1);
	       }
               break;
           case 'r':
               rate = strtol(argv[optind], NULL, 10);
               break;
           case 'q':
               seq_no = strtoul(argv[optind], NULL, 10);
               break;
           case 'l':
               length = strtoul(argv[optind], NULL, 10);
               break;
	   case 'f':
		f_hostname = argv[optind];
		break;
	   case 'h':
	   	f_port = strtol(argv[optind], NULL, 10);
		if (f_port <= 1024 || f_port >= 65536)
		{
		    perror(ERROR_PORT_RANGE);
		    exit(1);
		}
		break;
	   case 'i':
	   	priority = strtol(argv[optind], NULL, 10);
		break;
	   case 't':
		timeoutTime = strtol(optarg, NULL, 10);
	   	break;
           default:
               perror(ERROR_SENDER_USAGE);
	       exit(1);
       }
    }

    printf ("\033[34mSENDER SETUP WITH THE FOLLOWING CONFIG:\033[1;34m\n> in_port:\t%d\n> out_port:\t%d\n> send_rate:\t%d\n> seq_no:\t%u\n> length:\t%u\033[0m\n\n", in_port, out_port, rate, seq_no, length);

    if ( ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) )
    {
        perror(ERROR_SOCKET);
        exit(1);
    }

    // Set up in_port
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(in_port);

    if (bind(sockfd, (struct sockaddr *) &servaddr,
             sizeof(servaddr)) < 0)
    {
        perror(ERROR_BIND);
        exit(1);
    }

    dg_send(sockfd, &cliaddr, sizeof(cliaddr));

    return 0;
}
